#include "module.h"
#include <stdio.h>
#include <string.h>
#include <string.h>
#include <stdlib.h>
#include <stdbool.h>
#include <assert.h>

#include "token.h"
#include "ast.h"
#include "base.h"
#include "option.h"
#include "expr.h"
#include "stmt.h"
#include "buffer.h"
#include "common.h"
#include <set>
#include <string>
#include <map>
#include <stack>
#include <vector>

bool first_time = true;

static bool g_break_set = false;
static std::string g_break_file = "";
static int g_break_line;
static int g_last_cmd = 0;
static std::string g_break_method = "";

static int g_dbg_cmd = 0;
static env_t* g_dbg_env = g_top_env;
static int g_dbg_cur = 0;

std::pair<std::string, std::string> split(const std::string& s, char c)
{
    auto pos = s.find(c, 0);
    if (pos != std::string::npos) {
        std::string s1 = s.substr(0, pos);
        std::string s2 = s.substr(pos + 1);
        return std::make_pair(s1, s2);
    }
    return std::make_pair(s, "");
}

static bool is_blank(char c)
{
    return c == '\n' || c == '\t' || c == '\b' || c == '\r' || c == ' ';
}

static char buffer[1024];
static char cmd[1024];
static char cmd2[1024];
static char filename[1024];

struct simple_cmd
{
    const char* str;
    int cmd;
    const char* desc;   // description
};

static void print_symbol(env_t* env, const char* s)
{
    auto a = env->get_value(s);
    printf("%s:\n", s);
    print_any(a);
}

enum DBG_COMMAND {
    DBG_NONE = 0,
    DBG_PRINT_CMD,
    DBG_BREAK,
    DBG_GO,
    DBG_NEXT,
    DBG_NEXT_P,
    DBG_STEP,
    DBG_STEP_P,
    DBG_PRINT,
    DBG_PRINT_LOCAL,
};

static simple_cmd g_cmd[] = {
    { "",   DBG_NONE,      "do nothing" },
    { "pp", DBG_PRINT_CMD, "print all debug command" },
    { "b",  DBG_BREAK,     "set break point" },
    { "go", DBG_GO,        "go on running" },
    { "n",  DBG_NEXT,      "next statement" },
    { "np", DBG_NEXT_P,    "next statement with printing expr" },
    { "s",  DBG_STEP,      "step in" },
    { "sp", DBG_STEP_P,    "step in with printing expr" },
    { "p",  DBG_PRINT,     "print" },
    { "pl", DBG_PRINT_LOCAL, "print local" },
};

static void print_all_cmd();
static int find_cmd(const char* cmd);
static void get_cmd2();

static bool do_command(env_t* env, ast_node_t* node, int depth, int n)
{
    switch (n) {
    case DBG_NONE: {
        return g_last_cmd;
    }
    case DBG_PRINT_CMD: {
        print_all_cmd();
        return true;
    }
    case DBG_BREAK: {
        get_cmd2();
        if (cmd2[0] == 0) {
            printf("wrong break cmd format\n");
            return true;
        }
        auto pair = split(cmd2, ':');
        auto s1 = pair.first;
        auto s2 = pair.second;
        if (s1.size() > 0) {
            g_break_file = s1;
            g_break_set = true;
        }
        else {
            printf("wrong break cmd format\n");
            return true;
        }
        if (s2.size() > 0) {
            g_break_line = atoi(s2.c_str());
            g_break_set = true;
            g_break_method = "";
        }
        else {
            g_break_line = -1;
            g_break_method = s1;
            g_break_file = "";
            g_break_set = false;
        }
        return true;
    }
    case DBG_GO: {
        return false;
    }
    case DBG_NEXT: {
        g_option.debug_expr = false;
        g_dbg_env = env;
        g_dbg_cur = env->layer();
        return false;
    }
    case DBG_NEXT_P: {
        g_option.debug_expr = true;
        g_dbg_env = env;
        g_dbg_cur = env->layer();
        return false;
    }
    case DBG_STEP: {
        g_option.debug_expr = false;
        return false;
    }
    case DBG_STEP_P: {
        g_option.debug_expr = true;
        return false;
    }
    case DBG_PRINT: {
        get_cmd2();
        if (cmd2[0] != 0) {
            print_symbol(env, cmd2);
        }
        return true;
    }
    case DBG_PRINT_LOCAL: {
        env->print_symbols();
        return true;
    }
    default:
        printf("unexpected debug command: %d\n", n);
        return true;
    }
    return true;
}

static void print_all_cmd()
{
    for (int i = 0; i < COUNTOF(g_cmd); ++i) {
        int count = printf("\t%d. %s:", i, g_cmd[i].str);
        for (int j = count; j < 20; ++j) {
            printf(" ");
        }
        printf("%s\n", g_cmd[i].desc);
    }
}

static int find_cmd(const char* cmd)
{
    for (int i = 0; i < COUNTOF(g_cmd); ++i) {
        if (strcmp(g_cmd[i].str, cmd) == 0)
            return i;
    }
    return 0;
}

static int get_line(char* ss, int size)
{
    int count = 0;
    do {
        int c = getchar();
        if (c == '\n' || c == EOF)
            break;
        if (count < size - 2) {
            ss[count++] = c;
        }
        else {
            break;
        }
    } while (1);
    ss[count] = 0;
    ss[count + 1] = 0;
    return count;
}

static int get_cmd(char* ss, char* cmd, int size)
{
    int off = 0;
    int count = 0;
    do {
        char c = ss[off++];
        if (c == 0)
            break;
        if (is_blank(c)) {
            if (count == 0)
                continue;
            else
                break;
        }
        if (count < size - 1) {
            cmd[count++] = c;
        }
        else {
            break;
        }
    } while (1);
    cmd[count] = 0;
    return off;
}

static void get_buffer()
{
    get_line(buffer, (int)sizeof(buffer));
}

static int g_cmd_offset;

static void get_cmd1()
{
    g_cmd_offset = get_cmd(buffer, cmd, (int)sizeof(cmd));
}

static void get_cmd2()
{
    g_cmd_offset = get_cmd(buffer + g_cmd_offset, cmd2, (int)sizeof(cmd2));
}

static int get_debug_cmd()
{
    get_buffer();
    get_cmd1();
    if (cmd[0] == 0) {
        return g_last_cmd;
    }
    return find_cmd(cmd);
}

static void print_dbg(env_t* env, ast_node_t* node, int depth)
{
    fprint_location(stdout, &node->loc);
    printf("\n");
    std::stack<env_t*> stack;
    auto bak = env;
    while (env) {
        stack.push(env);
        env = env->get_parent();
    }
    while (stack.size() > 0) {
        env = stack.top();
        stack.pop();
        fprint_location(stdout, &env->loc);
        printf("%s\n", env->name);
    }
    if (bak != nullptr)
        bak->print_symbols();
    print_statement_ss(node, depth);
}

void do_debug(env_t* env, ast_node_t* node, int depth)
{
    bool do_break = false;
    if (g_dbg_cmd == DBG_STEP) {
        do_break = true;
        //mark_line();
        //printf("break by step\n");
    }
    else if (g_dbg_cmd == DBG_NEXT &&
        (env == g_dbg_env || env->layer() < g_dbg_cur)) {
        do_break = true;
        //mark_line();
        //printf("break by next\n");
    }
    if (first_time) {
        first_time = false;
        do_break = true;
        //mark_line();
        printf("break at start:\n");
    }
    if (g_break_set &&
        str_end_of(node->loc.filename, g_break_file.c_str()) &&
        (g_break_line == -1 || node->loc.line == g_break_line)) {
        //mark_line();
        do_break = true;
    }
    if (g_break_method.size() > 0 && env->name && g_break_method == env->name) {
        //mark_line();
        do_break = true;
    }
    // for changing code debug
    const char* f = nullptr;  // "ast.pcc";
    int line = 16;
    if (f && str_end_of(node->loc.filename, f) &&
        node->loc.line == line) {
        //mark_line();
        do_break = true;
    }
    if (do_break) {
        //printf("break here: ");
        //printf("\n");
        print_dbg(env, node, depth);
        do {
            g_dbg_cmd = get_debug_cmd();
        } while (do_command(env, node, depth, g_dbg_cmd));
    }
    g_last_cmd = g_dbg_cmd;
}
